Computer programs typically contain two types of code: instructions that carry out the purpose of the program and error handling code that contains instructions for recovering from or responding to unexpected or unusual conditions that occur. Error handling may include addressing anything from a file open failing because the file is not at a requested location to accessing memory that does not belong to the program (e.g., memory that the program did not allocate or has already freed). Software exceptions are one type of error condition notification paradigm. Using exceptions, a program typically surrounds a block of instructions with a “try” block and when an abnormal condition occurs, the program leaves the try block and executes one or more conditionally executed blocks.
An exception is the signal raised when a condition is detected that was not expected in the normal execution of a program thread. Many agents can detect incorrect conditions and raise exceptions. For applications running in a managed environment, exceptions can be raised by program code (or library code the program uses), the runtime engine, and unmanaged code that the application invokes. Exceptions raised on a thread of execution follow the thread through native and managed code and across application domains (e.g., Microsoft.NET AppDomains). If the program does not handle an exception, the exception is typically presented to the operating system and treated as an unhandled exception. While every managed exception has a type (e.g., System.ArgumentException or System.ArithmeticException), the type may only be meaningful in the context in which the exception is raised. A program can handle an exception if it understands the conditions that caused the exception to occur. However, if the program does not handle the exception, it could indicate any number of bad things to code remote from the point of failure that ultimately receives the exception. Once the exception has left the program, it only has one very general meaning: something bad has happened.
Exceptions represent a wide range of unexpected conditions during execution of a program. Exceptions may occur at a variety of levels when a program is viewed as a logical stack of layers. For example, the operating system may provide exceptions (e.g., structured exception handling (SEH)), an underlying runtime may provide exceptions (e.g., the C-language runtime or Microsoft.NET Common Language Runtime (CLR)), and the program itself may provide exceptions defined by the language used to create the program (e.g., C-language exceptions, Microsoft.NET exceptions, and so forth). For higher-level languages, the environment may wrap each of these exceptions into one or more types recognized by the language of the program. For example, Microsoft.NET represents any native exception as a specific managed exception that inherits from the Microsoft.NET Exception class.
Although providing multiple types of exceptions in a form familiar to developers increases the chance that a developer will be able to write code to handle an exception, this can also lead to a false sense that the developer has the ability to handle a particular exception correctly. Exception hierarchies encourage developers to catch overly broad classes of exceptions and lead to developers catching exceptions that represent error conditions that they cannot or often do not properly handle. For example, a developer may write a catch statement of the form “catch( . . . )” to catch all exceptions in C++ code or “catch(Exception e)” to catch all exceptions inheriting from the class “Exception” in Microsoft.NET languages. These types of constructs are perfectly valid in several programming languages and the result is that it is up to the code associated with the catch statement to correctly handle any type of exception that enters the catch block.
Some languages try to prevent developers from catching a broad class of exceptions. For example, C++ has exception specifications, a mechanism that allows a developer to specify which exceptions a particular function can raise. Java takes this a step further with checked exceptions, a compiler-enforced requirement that a certain class of exceptions be specified. In both languages, the developer lists the exceptions that can flow out of a function in the function declaration and callers are expected to handle those (and only those) exceptions. Exception specifications have had mixed results in practice.
For exceptions that corrupt application state (including corrupting applications, processes, and or runtimes), correct handling is difficult and often not performed correctly leading to unpredictable results. For example, a particular exception may indicate an access violation (AV), illegal instruction, use of freed memory, corruption of program data, and so forth. Continuing to execute at all in such an environment is risky, and those programs that do handle such exceptions correctly typically do so by performing a minimal amount of cleanup (taking care not to access potentially corrupt data structures) and exiting.